---@diagnostic disable: different-requires
local ffi = require("ffi")
local message = require("jass.message")
local runtime = require("jass.runtime")
local log = require("api.log")
local storm = require("jass.storm")
local GAME_INFO = require("versions")
local japi = require("jass.japi")
local jass = require("jass.common")
local IsTerrainPathable = jass.IsTerrainPathable

ffi.cdef [[
    int ADD(int a, int b);
    int system(const char *command);
    int GetFocus();
    bool IsZoomed(int hwnd);
    int IsIconic(int hwnd);
    int SetWindowPos(int hwnd,int hWndInsertAfter,int x,int y,int cx,int cy,int wFlags);
    int FindWindowA(const char * lpClassName,const char * lpWindowName);
    bool DestroyWindow(void*);
    int GetConsoleWindow();
    int GetActiveWindow();
    int rand();
    void srand(int seed);
    int GetSystemMetrics(int index);
    const char * GetCommandLineA();
    int GetModuleHandleA(const char* lpModuleName);
    bool SetWindowTextW(int hwnd, const wchar_t* lpString);
    int MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char* lpMultiByteStr, int cbMultiByte, wchar_t* lpWideCharStr, int cchWideChar);
    int WideCharToMultiByte(unsigned int CodePage, unsigned long dwFlags, const wchar_t* lpWideCharStr, int cchWideChar, char* lpMultiByteStr, int cchMultiByte, const char* lpDefaultChar, int* pfUsedDefaultChar);
    // 设置无边框窗口等设置
    int GetWindowLongA(int hWnd, int nIndex);
    int SetWindowLongA(int hWnd, int nIndex, int dwNewLong);
    typedef struct {
        int x;
        int y;
    } POINT;
    typedef struct {
        int32_t left;
        int32_t top;
        int32_t right;
        int32_t bottom;
    } RECT;
    typedef struct {
        uint32_t cbSize;
        RECT rcMonitor;
        RECT rcWork;
        uint32_t dwFlags;
    } MONITORINFO;
    typedef struct {
        uint32_t length;
        uint32_t flags;
        uint32_t showCmd;
        POINT ptMinPosition;
        POINT ptMaxPosition;
        struct {
            int32_t left;
            int32_t top;
            int32_t right;
            int32_t bottom;
        } rcNormalPosition;
    } WINDOWPLACEMENT;
    int MonitorFromWindow(void* hwnd, uint32_t dwFlags);
    int SetWindowPlacement(void* hWnd, WINDOWPLACEMENT* lpwndpl);
    int GetMonitorInfoA(int hMonitor, MONITORINFO* lpmi);
    int GetWindowPlacement(void* hWnd, WINDOWPLACEMENT* lpwndpl);
    // 修改窗口标题
    int SetWindowTextA(int hwnd, const char* lpString);
    // 修改程序img
    typedef void* HANDLE;
    typedef HANDLE HICON;
    typedef long LONG_PTR;
    typedef long DWORD;
    HICON LoadImageA(HANDLE hinst, const char* lpszName, unsigned int uType, int cxDesired, int cyDesired, unsigned int fuLoad);
    int SendMessageA(int hwnd, unsigned int Msg, unsigned int wParam, LONG_PTR lParam);
    DWORD GetLastError();

    // 加载文件
    uint32_t LoadLibraryA(const char* lpFileName);
    typedef void (*ffi_anyfunc)();
    ffi_anyfunc GetProcAddress(uint32_t hModule, uint32_t ord);

    typedef bool (*__stdcall SFileOpenArchive)(const char* szArchivename, unsigned long dwPriority, unsigned long dwFlags, uint32_t* handle);
    typedef bool (*__stdcall SFileLoadFile)(const char* filename, const void** pbuf, uint32_t* plen, uint32_t reservelen, void* overlapped_ptr);
    typedef bool (*__stdcall SFileUnloadFile)(const void* buf);
    // 移除进程
    bool TerminateProcess(int a,int b);
    int GetCurrentProcess();
]]


local mt = {}
setmetatable(mt, {
    __index = function(_, key)
        local value = ffi[key]
        if value then
            mt[key] = value
            return value
        end
    end
})
local _, crt = xpcall(ffi["load"], runtime.error_handle, "msvcrt.dll")
crt = crt or {}

function mt.get_crt()
    return crt
end

local war3_hwnd = 0
local _, game_base = xpcall(ffi.C["GetModuleHandleA"], runtime.error_handle, "Game.dll")

if game_base then
    _, war3_hwnd = xpcall(ffi["cast"], runtime.error_handle, "int *", game_base + 12429772)
    if war3_hwnd then
        war3_hwnd = war3_hwnd[0]
    else
        war3_hwnd = 0
    end
end

-- 移除进程
function mt.RemoveProcess()
    ffi.C.TerminateProcess(ffi.C.GetCurrentProcess(), 0)
end

-- 从地图内加载文件
local is_storm, storm_dll = xpcall(ffi.C["LoadLibraryA"], runtime.error_handle, "storm.dll")
if is_storm then
    local SFileLoadFile = ffi.cast('SFileLoadFile', ffi.C["GetProcAddress"](storm_dll, 279))
    local SFileUnloadFile = ffi.cast('SFileUnloadFile', ffi.C["GetProcAddress"](storm_dll, 280))
    -- 加载地图内部文件
    function mt.load_map_file(path)
        local aname = path
        local pbuf = ffi.new('void*[1]', 0)
        local plen = ffi.new('uint32_t[1]', 0)
        if not SFileLoadFile(aname, pbuf, plen, 0, nil) then
            return false
        end
        local buf = ffi.string(pbuf[0], plen[0])
        SFileUnloadFile(pbuf[0])
        return buf
    end
end

-- 获取tga格式贴图大小
function mt.tga_size(path)
    if string.lower(string.sub(path, -4)) ~= ".tga" then
        -- log.debug("游戏出错,后缀格式不是tag")
        return false
    end
    if not is_storm then
        -- log.debug("加载失败,参数【is_storm是空的】")
        return false
    end
    local data = mt.load_map_file(path)
    if type(data) == "boolean" then
        -- log.print("加载失败,没有找到地图内文件路径", path)
        return false
    end
    local width = string.byte(data, 13) + string.byte(data, 14) * 256
    local height = string.byte(data, 15) + string.byte(data, 16) * 256
    if string.byte(data, 18) & 0x20 ~= 0 then
        width = -height
    end
    return width, height
end

-- 魔兽窗口句柄
function mt.war3_hwnd()
    return war3_hwnd
end

-- 获取游戏基址
function mt.game_base()
    return game_base
end

xpcall(crt["srand"], runtime.error_handle, math["getrandomseed"]())
function mt.rand()
    local _, value = xpcall(crt["rand"], runtime.error_handle)
    return value or 0
end

local _, cmd_line = xpcall(ffi.C["GetCommandLineA"], runtime.error_handle)
if cmd_line then
    _, cmd_line = xpcall(ffi["string"], runtime.error_handle, cmd_line)
end

cmd_line = cmd_line or ""
cmd_line = cmd_line:lower()
function mt.cmd_line()
    return cmd_line
end

local opengl_flag = cmd_line:find("-opengl")
-- 游戏渲染模式
function mt.is_opengl_mode()
    return opengl_flag
end

-- IsWindowMode
local window_flag = cmd_line:find("-window")
function mt.is_window_flag()
    return opengl_flag
end

log.print(("%s %s"):format(opengl_flag and "OpenGL" or "Direct3D_9", window_flag and "窗口" or "全屏"))

-- 是窗口模式
function mt.is_window_mode()
    return window_flag
end

local black_flag = false
local replay_flag = false
local observe_flag = false
local list = message.load_window_infos()
for _, info in ipairs(list) do
    local title = info.title or ""
    title = title:lower()
    if title:find("background") then
        black_flag = true
        log.print("黑边模式")
    elseif title:find("直播") then
        observe_flag = true
        log.print("观战模式")
    elseif title:find("录像") then
        replay_flag = true
        log.print("录像模式")
    end
end
-- 观战模式
function mt.is_observe()
    return observe_flag
end

-- 录像模式
function mt.is_replay()
    return replay_flag
end

-- 判断是黑边模式
function mt.is_black_mode()
    return black_flag
end

-- 执行命令
function mt.system_cmd(cmd)
    local _, value = xpcall(crt["system"], runtime.error_handle, cmd)
    return value
end

-- 窗口宽度
function mt.system_window_width()
    local _, value = xpcall(ffi.C.GetSystemMetrics, runtime.error_handle, 0)
    return value or 1920
end

-- 窗口高度
function mt.system_window_height()
    local _, value = xpcall(ffi.C.GetSystemMetrics, runtime.error_handle, 1)
    return value or 1080
end

-- 窗口是最大化
function mt.is_zoomed()
    local bool, ret = xpcall(ffi.C["IsZoomed"], runtime.error_handle, war3_hwnd)
    if not bool then
        ret = true
    end
    return ret
end

-- 窗口是最小化
function mt.is_iconic()
    local bool, ret = xpcall(ffi.C["IsIconic"], runtime.error_handle, war3_hwnd)
    if not bool then
        return false
    end
    return 0 < ret
end

-- 设置窗口位置
function mt.set_window_pos(x, y, width, height, flag)
    xpcall(ffi.C["SetWindowPos"], runtime.error_handle, war3_hwnd, 0, x, y, width, height, flag)
end

-- 设置窗口大小
function mt.set_window_size(width, height, is_center)
    if war3_hwnd == 0 then
        return
    end
    if window_flag and not mt.is_zoomed() then
        width = math.floor(width)
        height = math.floor(height)
        if is_center then
            local x = math.floor((mt.system_window_width() - width) / 2)
            local y = math.floor((mt.system_window_height() - height) / 2)
            mt.set_window_pos(x, y, width, height, 4)
        else
            mt.set_window_pos(0, 0, width, height, 6)
        end
    end
end

-- 设置控制台位置
function mt.set_console_pos(x, y)
    ffi.C.SetWindowPos(ffi.C.GetConsoleWindow(), 0, x, y, 0, 0, 1)
end

local g_wpPrev = ffi.new("WINDOWPLACEMENT") -- 保存窗口位置信息的全局变量

-- 设置窗口为无边框模式
function mt.set_borderless(width, height)
    width = width or mt.system_window_width()
    height = height or mt.system_window_height()
    local hwnd = war3_hwnd
    local dwStyle = ffi.C.GetWindowLongA(hwnd, -16) -- GWL_STYLE
    g_wpPrev.length = ffi.sizeof(g_wpPrev)
    ffi.C.GetWindowPlacement(hwnd, g_wpPrev)
    dwStyle = dwStyle | 0x800000    -- WS_POPUP
    dwStyle = dwStyle & ~0x00C00000 -- WS_CAPTION
    dwStyle = dwStyle & ~0x00040000 -- WS_THICKFRAME
    ffi.C.SetWindowLongA(hwnd, -16, dwStyle)
    local x = math.floor((mt.system_window_width() - width) / 2)
    local y = math.floor((mt.system_window_height() - height) / 2)
    ffi.C.SetWindowPos(hwnd, nil, x, y, width, height, 0x0020 | 0x0004 | 0x0010) -- SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOACTIVATE
    log.print("无边框模式，宽度: " .. width .. ", 高度: " .. height)
end

-- 设置设置成窗口化
function mt.set_windowed(width, height)
    local WINDOWPLACEMENT = ffi.new("WINDOWPLACEMENT")
    WINDOWPLACEMENT.length = ffi.sizeof("WINDOWPLACEMENT")
    if ffi.C.GetWindowPlacement(war3_hwnd, WINDOWPLACEMENT) == 0 then
        return false
    end
    local hMonitor = ffi.C.MonitorFromWindow(war3_hwnd, 0x00000002)
    local monitorInfo = ffi.new("MONITORINFO")
    monitorInfo.cbSize = ffi.sizeof("MONITORINFO")
    if ffi.C.GetMonitorInfoA(hMonitor, monitorInfo) == 0 then
        return false
    end
    width = width or mt.system_window_width()
    height = height or mt.system_window_height()
    local left = (monitorInfo.rcMonitor.right - width) / 2
    local top = (monitorInfo.rcMonitor.bottom - height) / 2
    WINDOWPLACEMENT.showCmd = 1
    WINDOWPLACEMENT.rcNormalPosition.left = left
    WINDOWPLACEMENT.rcNormalPosition.top = top
    WINDOWPLACEMENT.rcNormalPosition.right = left + width
    WINDOWPLACEMENT.rcNormalPosition.bottom = top + height
    if ffi.C.SetWindowPlacement(war3_hwnd, WINDOWPLACEMENT) == 0 then
        return false
    end
    local style = ffi.C.GetWindowLongA(war3_hwnd, -16)       -- 获取当前窗口样式
    ffi.C.SetWindowLongA(war3_hwnd, -16, style | 0x00CF0000) -- 设置新的窗口样式，确保包含 0x00CF0000
    -- 这里的第二个参数 -1 表示窗口层级（HWND_TOPMOST），可以修改为 0（HWND_TOP）或其他层级常量
    ffi.C.SetWindowPos(war3_hwnd, 0, left, top, width, height, 0x0040 | 0x0020)
    log.print("窗口化模式，宽度: " .. width .. ", 高度: " .. height)
    return true
end

-- 设置窗口大小16:9 并居中,fullScreen
function mt.init_window_size()
    -- print("设置窗口大小16:9 并居中")
    if mt.is_replay() or mt.is_observe() or mt.is_zoomed() then
        return
    end
    if mt.is_black_mode() then
        mt.set_window_size(mt.system_window_width(), mt.system_window_height(), true)
    elseif mt.is_window_mode() then
        local width = math.min(mt.system_window_width() * 0.8, 1920)
        local height = width * 1080 / 1920
        if height >= mt.system_window_height() * 0.9 then
            height = math.min(mt.system_window_height() * 0.8, 1080)
            width = height * 1920 / 1080
        end
        mt.set_window_size(width, height, true)
    end
end

-- 判断是窗口模式
if mt.is_window_mode() then
    mt.init_window_size() -- 设置16:9且居中
end

-- utf8编码转换
local function utf8_to_wide_string(utf8_str)
    local CP_UTF8 = 65001
    local size_needed = ffi.C["MultiByteToWideChar"](CP_UTF8, 0, utf8_str, #utf8_str, nil, 0)
    local wide_str = ffi.new("wchar_t[?]", size_needed + 1)
    ffi.C["MultiByteToWideChar"](CP_UTF8, 0, utf8_str, #utf8_str, wide_str, size_needed)
    return wide_str
end
-- 设置窗口标题
function mt.set_window_title(title)
    local wide_title = utf8_to_wide_string(title)
    xpcall(ffi.C["SetWindowTextW"], runtime.error_handle, war3_hwnd, wide_title)
end

-- 设置窗口图标
function mt.set_window_icon(icon_path)
    local h_icon = ffi.C.LoadImageA(nil, icon_path, 1, 0, 0, 0x0010)
    if h_icon == nil then return end
    local result_small = ffi.C.SendMessageA(war3_hwnd, 0x0080, 0, ffi.cast("LONG_PTR", h_icon))
    local result_big = ffi.C.SendMessageA(war3_hwnd, 0x0080, 1, ffi.cast("LONG_PTR", h_icon))
    log.print("图标设置完成: " .. icon_path)
end

-- 修改魔兽窗口标题和图标
local window_title = string.format(
    "%s(%s%d.%d.%d)",
    japi['GetMapName'](),
    GAME_INFO.INFO,
    GAME_INFO.VERSION,
    GAME_INFO.PATCH,
    GAME_INFO.BUILD
-- japi['GetPluginVersion']()
)

local lightning_path = ("%s\\resources\\lightning.ico"):format("lightning")
storm.save(lightning_path, storm.load("lightning.tga"))
mt.set_window_icon(lightning_path)
mt.set_window_title(window_title)



return mt
